package chpt_05_03_proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.aspectj.lang.annotation.Before;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Ping
 * @date 2023/4/13 10:30
 **/
public class ProxyDemo {
    static List<User> userList ;
    static {
        userList = new ArrayList<>() ;
        userList.add(new User("1", "zhangsan", "123")) ;
        userList.add(new User("2", "lisi", "123")) ;
        userList.add(new User("3", "wangwu", "123")) ;
        userList.add(new User("4", "zhaoliu", "123")) ;

    }
    public static void main(String args[]) {
        //testForStaticProxy();
       // testForDyanmicProxy() ;
        testForCGLibProxy() ;
    }

    public static void testForStaticProxy() {
        UserDao userDao = new UserDaoCachedProxy(new UserDaoImpl()) ;
        System.out.println(userDao.queryById("1") );
        System.out.println(userDao.queryById("1") );
    }

    public static void testForDyanmicProxy() {
//        UserDao userDaoimpl = new UserDaoImpl() ;
//        UserDao userDao = (UserDao) new DynamicProxyInstanceFactoryByReflection(
//                  userDaoimpl, new CachedHandler(userDaoimpl))
//                .newProxyInstance();
//        System.out.println(userDao.queryById("1") );
//        System.out.println(userDao.queryById("1") );


    }

    public static void testForCGLibProxy() {
        /**
         * subject
         */
        UserDao userDaoimpl = new UserDaoImpl() ;
        /**
         * before and after advices
         */
        BeforeAdv simpleBeforeAdv = new SimpleLogBeforeAdv() ;
        AfterAdv simpleAfterAdv = new SimpleLogAfterAdv() ;
        /**
         * get proxy instance by cglib
         */
        UserDao userDao = (UserDao) new ProxyFactoryByCGLib(
                             userDaoimpl,   // target
                            simpleBeforeAdv, // beforeAdv and after adv
                            simpleAfterAdv).getProxy() ;
        System.out.println(userDao.queryById("1") ) ;


    }
}

class User {
    private String id ;
    private String name ;
    private String password ;

    public User(String id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

/**
 * subject
 */
interface UserDao {
    public User queryById(String id) ;
}

/**
 * real subject
 */
class UserDaoImpl implements  UserDao {
    @Override
    public User queryById(String id) {
        System.out.println("[INFO] query from UserDaoImpl.") ;
        for (User user : ProxyDemo.userList) {
            if (user.getId() == id) {
                return user ;
            }
        }
        return null;
    }
}

/**
 * proxy part:
 * purpose:  caching the query result .
 * two methods:
 * 1. by static proxy: the relation between subject and proxy was fixed before execution
 * 2. by dynamic proxy:
 *
 */

class UserDaoCachedProxy implements UserDao {
    /**
     * subject to proxy
     */
    private UserDao userDao ;
    private Map<String, User> cachedUserMap = new HashMap<>();

    public UserDaoCachedProxy(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public User queryById(String id) {
        if (cachedUserMap.containsKey(id)) {
            System.out.println("[INFO] get user from cached map.") ;
            return this.cachedUserMap.get(id) ;
        }
        User user = this.userDao.queryById(id) ;
        this.cachedUserMap.put(user.getId(), user) ;
        return user;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    Proxy proxy ;
}

/**
 * Dyanmic proxy: java reflection
 *  Proxy.newInstance
 *  1. invocationHandler: implement your own logic
 *  2. invoke Proxy newProxyInstance(classloader, interfaces, invocationhandler)
 */

/**
 *  Dynamic Proxy instance factory implemented by java reflection
 */
class DynamicProxyInstanceFactoryByReflection {
    /**
     * subject to be proxy.
     */
    private Object target;
    /**
     * enhanced logic
     */
    private InvocationHandler handler ;

    public DynamicProxyInstanceFactoryByReflection(Object target,
                                                   InvocationHandler handler) {
        this.target = target;
        this.handler = handler;
    }

    /**
     * create proxy instance by {@link Proxy}.
     * @return
     */
    public Object newProxyInstance() {

        ClassLoader loader = DynamicProxyInstanceFactoryByReflection.class.getClassLoader() ;
        /**
         * get interfaces implemented by target.
         */
        Class[] interfaces = this.target.getClass().getInterfaces() ;

        return Proxy.newProxyInstance(loader, interfaces, handler) ;

    }
}

class CachedHandler implements InvocationHandler {
    private UserDao userDao ;
    private Map<String, User> userMap = new HashMap<>();

    public CachedHandler(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        /**
         * before logic
         */
        String id = (String)args[0] ;
        if (this.userMap.containsKey(id)) {
            System.out.println("[INFO] get user from cached map.") ;
            return this.userMap.get(id) ;
        }

        /**
         * userDao 中的方法
         */
        Object ret = method.invoke(this.userDao, args) ;
        this.userMap.put(id, (User)ret) ;
        return ret;
    }
}
//-------------------------cglib-------

interface BeforeAdv {
    public void before() ;
}
interface AfterAdv {
    public void after() ;
}

class SimpleLogBeforeAdv implements BeforeAdv {
    @Override
    public void before() {
        System.out.println("[LOG] before invoke.") ;
    }
}

class SimpleLogAfterAdv implements AfterAdv {
    @Override
    public void after() {
        System.out.println("[LOG] after invoke.") ;
    }
}




/**
 * role: proxy factory
 */
class ProxyFactoryByCGLib implements MethodInterceptor {
    private Object target ;
    private BeforeAdv before ;
    private AfterAdv after ;

    public ProxyFactoryByCGLib(Object target, BeforeAdv before, AfterAdv after) {
        this.target = target;
        this.before = before;
        this.after = after;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        this.before.before();
        Object ret = methodProxy.invoke(this.target, objects) ;
        this.after.after();
        return ret;
    }
    public Object getProxy() {
        Enhancer enhancer = new Enhancer() ;
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return enhancer.create() ;

    }
}


















